package com.leyou.search.service;

import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exceptions.LyException;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.JsonUtils;
import com.leyou.common.vo.PageResult;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.*;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.pojo.Goods;
import com.leyou.search.repository.GoodsRepository;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author 虎哥
 */
@Service
public class SearchService {

    @Autowired
    private ItemClient itemClient;

    /**
     * 封装一个Goods对象
     */
    public Goods buildGoods(SpuDTO spu) {
        Long spuId = spu.getId();
        // 1.搜索字段，包含名称、分类、品牌等
        // 1.1.查询分类名称
        String categoryName = spu.getCategoryName();
        if (StringUtils.isBlank(categoryName)) {
            categoryName = itemClient.queryCategoryByIds(spu.getCategoryIds())
                    .stream()
                    .map(CategoryDTO::getName)
                    .collect(Collectors.joining());
        }
        // 1.2.查询品牌名称
        String brandName = spu.getBrandName();
        if (StringUtils.isBlank(brandName)) {
            brandName = itemClient.queryBrandById(spu.getBrandId()).getName();
        }
        // 1.3.拼接
        String all = spu.getName() + categoryName + brandName;

        // 2.spu下的所有sku的集合，转为json
        // 2.1.查询skuDTO
        List<SkuDTO> skuDTOList = spu.getSkus();
        if (CollectionUtils.isEmpty(skuDTOList)) {
            skuDTOList = itemClient.querySkuBySpuId(spuId);
        }
        // 2.2.取出sku中我们需要的数据
        List<Map<String, Object>> skuList = new ArrayList<>();
        // 2.3.定义价格的集合
        Set<Long> price = new TreeSet<>();
        for (SkuDTO skuDTO : skuDTOList) {
            // 转换sku
            Map<String, Object> sku = new HashMap<>();
            sku.put("id", skuDTO.getId());
            sku.put("title", skuDTO.getTitle());
            sku.put("image", StringUtils.substringBefore(skuDTO.getImages(), ","));
            sku.put("price", skuDTO.getPrice());
            skuList.add(sku);
            // 添加价格
            price.add(skuDTO.getPrice());
        }

        // 3.sku的所有价格的集合

        // 4.商品中用来搜索的规格参数的键值对。key：规格参数名称; value：规格参数的值
        Map<String, Object> specs = new HashMap<>();

        // 4.1.查询规格参数key，specParam表中，当前分类下的searching为true的规格
        List<SpecParamDTO> params = itemClient.querySpecParams(null, spu.getCid3(), true);

        // 4.2.查询规格参数值,去SpuDetail中查询
        SpuDetailDTO detail = spu.getSpuDetail();
        if (detail == null) {
            detail = itemClient.querySpuDetailById(spuId);
        }
        // 4.2.1.通用规格参数值
        Map<Long, Object> genericSpec = JsonUtils.toMap(detail.getGenericSpec(), Long.class, Object.class);
        // 4.2.2.特有规格参数值
        Map<Long, Object> specialSpec = JsonUtils.toMap(detail.getSpecialSpec(), Long.class, Object.class);
        // 4.2.3.合并规格参数
        genericSpec.putAll(specialSpec);


        // 4.3.把key和value匹配成对，存入specs
        for (SpecParamDTO param : params) {
            // 取出参数名称，作为specs中的key
            String key = param.getName();
            // 获取value
            Object value = genericSpec.get(param.getId());

            // 判断是否需要做区间划分（是否为数值类型）
            if (param.getNumeric()) {
                // 判断value值在哪一个区间，把区间作为值，存入specs
                value = chooseSegment(value, param);
            }
            // 存入specs
            specs.put(key, value);
        }

        // 5.创建goods对象，填装数据
        Goods goods = new Goods();
        goods.setSubTitle(spu.getSubTitle());
        goods.setId(spuId);
        goods.setCategoryId(spu.getCid3());
        goods.setBrandId(spu.getBrandId());
        // 搜索字段，包含名称、分类、品牌等
        goods.setAll(all);
        goods.setCreateTime(spu.getCreateTime().getTime());
        // sku的所有价格的集合
        goods.setPrice(price);
        // spu下的所有sku的集合，转为json
        goods.setSkus(JsonUtils.toString(skuList));
        // 商品中用来搜索的规格参数的键值对。key：规格参数名称; value：规格参数的值
        goods.setSpecs(specs);
        return goods;
    }

    private String chooseSegment(Object value, SpecParamDTO p) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }

    @Autowired
    private ElasticsearchTemplate esTemplate;

    public PageResult<GoodsDTO> search(SearchRequest request) {
        // 1.创建查询构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        // 2.准备查询条件
        // 2.0.source过滤
        queryBuilder.withSourceFilter(
                new FetchSourceFilter(new String[]{"id", "skus", "subTitle"}, null));

        // 2.1.准备搜索关键字条件
        queryBuilder.withQuery(buildBasicQuery(request));

        // 2.2.分页条件
        int page = request.getPage() - 1;
        int size = request.getSize();
        queryBuilder.withPageable(PageRequest.of(page, size));

        // 3.查询结果
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);

        // 4.解析结果
        // 4.1.处理分页结果
        long total = result.getTotalElements();
        int totalPage = result.getTotalPages();
        List<Goods> goodsList = result.getContent();
        // 4.2.DTO转换
        List<GoodsDTO> list = BeanHelper.copyWithCollection(goodsList, GoodsDTO.class);
        return new PageResult<>(total, totalPage, list);
    }

    private QueryBuilder buildBasicQuery(SearchRequest request) {
        // 0.健壮性判断
        String key = request.getKey();
        if (StringUtils.isBlank(key)) {
            // 请求参数为空，不返回任何结果
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }

        // 	1.创建出一个布尔查询
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        //	2.利用must添加关键字搜索条件
        queryBuilder.must(QueryBuilders.matchQuery("all", request.getKey()));
        //	3.从请求中取出过滤项的map
        Map<String, String> filters = request.getFilter();
        if(!CollectionUtils.isEmpty(filters)){
            // 遍历，得到要过滤的键值对
            for (Map.Entry<String, String> entry : filters.entrySet()) {
                // 取出搜索条件的名称
                String name = entry.getKey();
                // 取出搜索条件的值
                String value = entry.getValue();
                //	4.对过滤项的key做处理，分类、品牌需要处理成categoryId和brandId，其它过滤项要加specs
                if("品牌".equals(name)){
                    name = "brandId";
                } else if("分类".equals(name)){
                    name = "categoryId";
                }else{
                    name = "specs." + name;
                }
                //	5.利用filter添加其它过滤条件
                queryBuilder.filter(QueryBuilders.termQuery(name, value));
            }
        }
        return queryBuilder;
    }

    public Map<String, List<?>> getFilter(SearchRequest request) {
        // 	1.构建查询条件（与查询数据的条件一致）
        NativeSearchQueryBuilder queryBuilder = buildSearchQueryBuilder(request);

        //	2.添加聚合条件（分类和品牌）
        queryBuilder.addAggregation(AggregationBuilders.terms("categoryAgg").field("categoryId"));
        queryBuilder.addAggregation(AggregationBuilders.terms("brandAgg").field("brandId"));

        //	3.查询数据
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);

        //	4.解析结果（只要聚合结果）解析出来的是分类或品牌的id
        Aggregations aggregations = result.getAggregations();
        // 4.1.根据聚合名称，获取聚合结果
        List<Long> categoryIds = getIdListFromAggregation(aggregations, "categoryAgg");
        // 4.2.根据聚合名称，获取聚合结果
        List<Long> brandIds = getIdListFromAggregation(aggregations, "brandAgg");

        //	5.根据id查询分类和品牌
        // 5.1.查询分类
        List<CategoryDTO> categoryList = itemClient.queryCategoryByIds(categoryIds);
        // 5.2.查询品牌
        List<BrandDTO> brandList = itemClient.queryBrandByIds(brandIds);

        //	6.封装并返回
        Map<String, List<?>> filterList = new LinkedHashMap<>();
        filterList.put("分类", categoryList);
        filterList.put("品牌", brandList);

        // 7.处理分类下的规格参数的聚合.判断分类是否只剩下一个
        if(categoryIds != null && categoryIds.size() == 1){
            // 如果只剩下一个分类，才去聚合规格。传参：分类的id，查询条件，放结果的map
            handleSpecAggreagation(categoryIds.get(0), request, filterList);
        }
        return filterList;
    }

    private void handleSpecAggreagation(Long cid, SearchRequest request, Map<String, List<?>> filterList) {
        // 	1.拿到分类，根据分类查询规格参数***
        List<SpecParamDTO> params = itemClient.querySpecParams(null, cid, true);

        //	2.构建查询条件（与查询数据的条件一致）
        NativeSearchQueryBuilder queryBuilder = buildSearchQueryBuilder(request);

        //	3.添加聚合条件（查询到的每一个规格参数，循环） ***
        for (SpecParamDTO param : params) {
            // 取出规格参数名称，作为聚合名称。将来获取聚合结果，还要用这个名称
            String name = param.getName();
            queryBuilder.addAggregation(AggregationBuilders.terms(name).field("specs." + name));
        }

        //	4.查询数据
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);

        //	5.解析结果（只要聚合结果）对每一个规格都解析，得到就是过滤项 ***
        Aggregations aggregations = result.getAggregations();
        // 循环遍历规格，根据规格名称，获取聚合的结果
        for (SpecParamDTO param : params) {
            // 规格名称，就是聚合名称
            String name = param.getName();
            // 根据名称获取聚合
            Terms terms = aggregations.get(name);
            // 从桶中取结果
            List<String> list = terms.getBuckets()
                    .stream()
                    .map(Terms.Bucket::getKeyAsString)
                    .filter(StringUtils::isNotBlank)
                    .collect(Collectors.toList());
            //	6.封装并返回
            filterList.put(name, list);
            /*          // 获取桶
            List<? extends Terms.Bucket> buckets = terms.getBuckets();
            // 过滤项的集合
            List<String> list = new ArrayList<>();
            // 遍历从桶中取出过滤项
            for (Terms.Bucket bucket : buckets) {
                String key = bucket.getKeyAsString();
                if(StringUtils.isNotBlank(key))
                    list.add(key);
            }
*/
        }
    }

    private NativeSearchQueryBuilder buildSearchQueryBuilder(SearchRequest request) {
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 1.0.source过滤
        queryBuilder.withSourceFilter(
                new FetchSourceFilter(new String[]{""}, null));

        // 1.1.准备搜索关键字条件
        queryBuilder.withQuery(buildBasicQuery(request));

        // 1.2.分页条件
        queryBuilder.withPageable(PageRequest.of(0, 1));
        return queryBuilder;
    }

    private List<Long> getIdListFromAggregation(Aggregations aggregations, String aggName) {
        // 根据聚合名称，获取聚合结果
        Terms categoryTerms = aggregations.get(aggName);
        return categoryTerms.getBuckets()
                .stream()
                .map(b -> b.getKeyAsNumber().longValue())
                .collect(Collectors.toList());

        /*// 获取桶
        List<? extends Terms.Bucket> buckets = categoryTerms.getBuckets();
        // 从桶中取出所有的key
        List<Long> idList = new ArrayList<>();
        for (Terms.Bucket bucket : buckets) {
            Number num = bucket.getKeyAsNumber();
            long id = num.longValue();
            idList.add(id);
        }
        return idList;*/

    }

    @Autowired
    private GoodsRepository repository;

    public void insertGoods(Long spuId) {
        // 查询spu
        SpuDTO spu = itemClient.querySpuById(spuId);
        // 构建Goods
        Goods goods = buildGoods(spu);
        // 新增goods
        repository.save(goods);
    }

    public void deleteGoodsById(Long spuId) {
        // 删除
        repository.deleteById(spuId);
    }
}
